#pragma once
#include "Util/Log.hpp"
#include "Util/Util.hpp"
#include "match.hpp"
#include "db.hpp"
#include "room.hpp"
#include "session.hpp"
#include "online_manager.hpp"
using namespace LogModel;
using namespace std::placeholders;

const std::string host = "127.0.0.1";
const std::string user = "root";
const std::string passwd = "123456";
const std::string dbname = "GoBang"; // 要访问的数据库的名字
unsigned int port = 3306;

const std::string static_resource_root = "./Resource"; // 静态资源目录

class GoBang_Server
{
public:
    // 需要注意这些成员变量声明在private那里的顺序和初始化的顺序.
    // 初始化顺序由声明顺序主导，而非初始化列表顺序
    GoBang_Server()
        : _ut(host, user, passwd, dbname, port),
          _web_root(static_resource_root),
          _sm(&_server),
          _rm(&_ut, &_om),
          _mc(&_rm, &_om, &_ut)
    {
        // 1.设置服务器的日志等级  (none: 设置websocketpp库内部的日志为不打印)
        _server.set_access_channels(websocketpp::log::alevel::none);
        // 2.初始化asio(异步IO)调度器
        _server.init_asio();
        // 3.端口号可以快速重新绑定
        _server.set_reuse_addr(true);
        // 4.注册回调函数
        // 注意：类内成员函数在绑定的时候,需要指明类作用域和取地址

        // 4.1.注册处理http请求的回调函数,客户端给服务器发送http请求时,触发此回调
        _server.set_http_handler(std::bind(&GoBang_Server::http_callback, this, _1));

        // 4.2.注册服务器长连接建立成功之后的回调函数,当服务器长连接建立成功之后触发此回调
        _server.set_open_handler(std::bind(&GoBang_Server::open_callback, this, _1));

        // 4.3.注册服务器长连接断开之前的回调函数,当服务器长连接断开之前触发此回调
        _server.set_close_handler(std::bind(&GoBang_Server::close_callback, this, _1));

        // 4.4注册服务器处理客户端的websocket长连接通信请求的回调函数,当客户端长连接向服务器通信时触发此回调
        _server.set_message_handler(std::bind(&GoBang_Server::message_callback, this, _1, _2));

        // 4.5 可以通过该接口注册一个回调函数,在函数内部,可以自主决定是否升级http协议为websocket协议
        //  _server.set_validate_handler(xxxx);
    }
    ~GoBang_Server()
    {
    }

private:
    // 处理http请求: 注意是http请求
    void http_callback(websocketpp::connection_hdl hdl)
    {
        WsServer_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        // 请求路由派发:

        // 2.注册请求
        if (method == "POST" && uri == "/reg")
        {
            Register(conn);
        }
        // 2.登录请求
        else if (method == "POST" && uri == "/login")
        {
            Login(conn);
        }
        // 3.进入大厅后,用户个人信息请求
        else if (method == "GET" && uri == "/info")
        {
            Info(conn);
        }
        else
        {
            // 4.静态资源请求:
            Resource(conn);
        }
    }

    // 当客户端向服务器发送websocket长连接建立请求时,websocketpp库会自动建立连接并给客户端返回HTTP 101 状态码响应
    // 下面是服务器websocket长连接建立成功之后的回调处理 //这里的功能是: 管理已经建立好的websocket长连接
    void open_callback(websocketpp::connection_hdl hdl)
    {
        // 注意注意! ! ! !
        // 此时connection_ptr从 HTTP 连接句柄转变为 WebSocket 连接句柄, 后续操作均基于 WebSocket 协议进行

        // 在 websocketpp 中，​​在websocket长连接建立成功之后的回调函数中通过 connection_ptr 获取原始 HTTP
        // 请求的 URI 是完全可行的​​。
        // 因为 websocketpp 的连接对象（server::connection_type）在完成 HTTP 到 WebSocket 的升级后，
        // ​​仍会保留原始 HTTP 协议升级请求的完整信息​（包括 URI、请求头、客户端地址等），以便开发者在后续逻辑中
        // 使用这些信息（例如基于 URI 的连接管理）。

        // 注意: websocketpp 的连接对象（server::connection_type）在连接建立时会完整保存原始 HTTP 协议升级请求时的
        //      报文信息（包括 URI、头部、客户端地址等），即使在连接关闭时，这些信息仍然有效​​（直到连接对象被销毁）

        // 1. 获取客户端websocket连接句柄
        WsServer_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
        // 2. 获取websocket完整报文
        websocketpp::http::parser::request req = conn->get_request();
        // 3. 获取uri
        std::string uri = req.get_uri();

        // HTTP协议升级为websocket协议的客户端请求报文中的URL格式为: "ws://117.72.218.219/xxx"
        // URI是/xxx , 我们正是通过URI来管理不同的websocket长连接！！！！！！

        //  4.通过uri来区分不同的websocket长连接:
        if (uri == "/hall")
        {
            // 建立的长连接是游戏大厅的长连接
            return websocket_game_hall(conn);
        }
        else if (uri == "/room")
        {
            // 建立的长连接是游戏房间的长连接
            return websocket_game_room(conn);
        }
    }

    // 假如客户端在游戏大厅或者游戏房间内,且对应长连接已经建立好了,当用户跳转到别的任何页面时,当前页面的websocket长
    // 连接都会被自动关闭(页面销毁了,对应websocket对象也就没了)
    //  websocket长连接断开 “之前” 的回调处理  //这里的功能是: 删除服务端管理的websocket长连接 , 否则系统资源泄露
    void close_callback(websocketpp::connection_hdl hdl)
    {
        // 注意: websocketpp 的连接对象（server::connection_type）在连接建立时会完整保存原始 HTTP 协议升级请求时的
        //      报文信息（包括 URI、头部、客户端地址等），即使在连接关闭时，这些信息仍然有效​​（直到连接对象被销毁）

        // 这里也需要先分辨出该长连接是哪种类型的长连接(游戏大厅长连接、游戏房间长连接)

        WsServer_t::connection_ptr conn = _server.get_con_from_hdl(hdl); // websocket长连接对象
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        // 1.通过uri来区分不同的websocket长连接请求:
        if (uri == "/hall")
        {
            // 需要移除管理的长连接是游戏大厅的长连接
            // 该websocket长连接断开的原因：1.直接退出游戏了  2.从游戏大厅进入游戏房间
            return remove_game_hall_websocket(conn);
        }
        else if (uri == "/room")
        {
            // 需要移除管理的长连接是游戏房间的长连接
            // 该websocket长连接断开的原因：1.直接退出游戏了  2.从游戏房间回到游戏大厅
            return remove_game_room_websocket(conn);
        }
    }

    // websocket长连接通信处理 : 包括游戏大厅内和游戏房间内
    void message_callback(websocketpp::connection_hdl hdl, WsServer_t::message_ptr msg)
    {
        WsServer_t::connection_ptr conn = _server.get_con_from_hdl(hdl); // websocket长连接对象
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        // 1.通过uri来区分不同的websocket长连接消息请求:
        if (uri == "/hall")
        {
            // 需要处理的是游戏大厅的长连接通信消息
            return message_game_hall_websocket(conn, msg);
        }
        else if (uri == "/room")
        {
            // 需要处理的是游戏房间内的长连接通信消息
            return message_game_room_websocket(conn, msg);
        }
    }

private:
    // 处理游戏大厅的长连接通信消息
    // 客户端在游戏大厅会发出的websocket通信请求只有: 1.开始匹配  2.停止匹配
    void message_game_hall_websocket(WsServer_t::connection_ptr &conn, WsServer_t::message_ptr &msg)
    {
        // 1.判断会话信息是否存在,并根据会话信息找到用户id  //身份验证
        session_ptr session = get_session_by_cookie(conn);
        if (session.get() == nullptr)
            return;

        // 2.获取请求信息
        std::string request_body = msg->get_payload(); // 序列化的

        Json::Value root;
        bool ret1 = JSON_Util::DeSerialize(request_body, root); // 反序列化
        if (!ret1)
        {
            // 反序列化失败
            root["result"] = false;
            root["reason"] = "匹配请求信息解析失败";
            std::string resp_body;
            JSON_Util::Serialize(root, resp_body);
            conn->send(resp_body);
            return;
        }

        std::string optype = root["optype"].asCString();
        // 3.请求信息处理
        if (optype == "match_start")
        {
            // 3.1开始匹配对战: 将用户添加到匹配队列里
            _mc.add(session->get_uid());
            // 给客户端响应
            websocket_send(conn, "match_start", true, "");
            return;
        }
        else if (optype == "match_stop")
        {
            // 3.2停止匹配: 将用户从匹配队列中移除
            _mc.remove(session->get_uid());
            // 给客户端响应
            websocket_send(conn, "match_stop", true, "");
            return;
        }
        else
        {
            LOG(LogLevel::ERROR) << "unknown optype";
            return;
        }
    }
    void message_game_room_websocket(WsServer_t::connection_ptr &conn, WsServer_t::message_ptr &msg)
    {
        // 1.获取客户端session,判断客户端是否已经登录,并根据会话信息找到用户id  //身份验证
        session_ptr session = get_session_by_cookie(conn);
        if (session.get() == nullptr)
            return;

        // 2.获取客户端的房间信息:
        //   玩家有那么多,所以就有很多的房间,如果客户端的报文不携带房间信息
        //   那我(服务端)怎么知道是哪个房间中的用户发来操作请求?又怎么知道在哪个房间处理业务?
        Room_ptr rp = _rm.get_room_by_uid(session->get_uid());
        if (rp.get() == nullptr)
        {
            return websocket_send(conn, "unknown", false, "找不到房间信息");
        }

        // 3.提取websocket请求报文,并反序列化
        Json::Value root;
        std::string req_body = msg->get_payload();

        LOG(LogLevel::DEBUG) << req_body;

        bool ret = JSON_Util::DeSerialize(req_body, root);
        if (!ret)
        {
            return websocket_send(conn, "unknown", false, "反序列化房间请求信息失败");
        }

        // 4.房间模块进行具体的业务处理(下棋或者聊天)
        rp->handle_request(root); // rp是该玩家所在的房间
    }

private:
    // 建立游戏大厅长连接 "成功" 之后的处理:  管理已经建立好的游戏大厅websocket长连接
    void websocket_game_hall(WsServer_t::connection_ptr &conn)
    {
        // 1. 判断用户是否已经登录(当前用户的会话是否还存在)
        session_ptr session = get_session_by_cookie(conn);
        if (session.get() == nullptr)
            return;

        // 2. 判断当前客户端是否是重复登录（即一个账号不能在两个地方同时登录）
        // 如果用户在线管理那里(游戏大厅长连接或者游戏房间长连接)已经有了当前用户的websocket长连接
        // 就属于重复登录
        int uid = session->get_uid();
        if (_om.is_in_game_hall(uid) || _om.is_in_game_room(uid))
        {
            return websocket_send(conn, "hall_ready", false, "账号已经在别处登录");
        }

        // 3. 将当前用户以及其长连接加入到游戏大厅(在线用户管理)
        _om.Enter_game_hall(uid, conn);

        // 4. 给客户端响应大厅长连接建立成功
        websocket_send(conn, "hall_ready", true, "");

        // 5.将会话生命周期设置为永久
        _sm.set_session_timer(session->get_ssid(), SESSION_PERMANENT);
    }

    // 建立游戏房间长连接 "成功" 之后的处理:  管理已经建立好的游戏房间websocket长连接
    void websocket_game_room(WsServer_t::connection_ptr &conn)
    {
        // 1.会话检查(验证当前玩家是否已经登录)
        session_ptr session = get_session_by_cookie(conn);
        if (session.get() == nullptr)
            return;

        // 2. 判断当前客户端是否是重复登录（即一个账号不能在两个地方同时登录）
        // 注意,这里和上面websocket_game_hall不同的是: 只有在游戏大厅页面才会跳转到游戏房间页面
        // 当玩家进入游戏房间页面后,游戏大厅的长连接就自动被销毁的,服务器管理的对应长连接也被删除了
        // 所以如果用户在线管理那里的游戏房间长连接已经有了当前用户的websocket长连接
        // 就属于重复登录
        int uid = session->get_uid();
        if (_om.is_in_game_room(uid))
        {
            return websocket_send(conn, "room_ready", false, "账号已经在别处登录");
        }

        // 3.判断服务器是否已经为该用户创建了房间 (因为在线用户管理和房间管理没有关系)
        Room_ptr room_ptr = _rm.get_room_by_uid(uid);
        if (room_ptr.get() == nullptr)
        {
            return websocket_send(conn, "room_ready", false, "找不到房间信息");
        }

        // 4.将游戏房间长连接加入到在线用户管理模块 (有房间才能管理房间长连接)
        _om.Enter_game_room(uid, conn);

        // 5. 给客户端响应房间长连接建立成功,并且将房间信息传给客户端
        Json::Value resp;
        resp["optype"] = "room_ready";
        resp["result"] = true;
        resp["roomid"] = room_ptr->get_room_id();
        resp["uid"] = session->get_uid();                // 用户自己的id
        resp["white_id"] = room_ptr->get_white_userid(); // 白棋用户id
        resp["black_id"] = room_ptr->get_black_userid(); // 黑棋用户id

        std::string resp_body;
        JSON_Util::Serialize(resp, resp_body); // 序列化
        conn->send(resp_body);

        // 6. 将会话生命周期设置为永久(因为删除游戏大厅长连接管理那里将会话设置为了具备生命周期)
        _sm.set_session_timer(session->get_ssid(), SESSION_PERMANENT);
    }

    // 在websocket长连接建立的情况下,给客户端发送响应
    void websocket_send(WsServer_t::connection_ptr &conn, const std::string &optype,
                        bool result, const std::string &reason)
    {
        Json::Value resp;
        resp["optype"] = optype;
        resp["result"] = result;
        resp["reason"] = reason;
        std::string resp_body;
        JSON_Util::Serialize(resp, resp_body);
        // 注意: 由于此时已经是websocket长连接,所以服务端给客户端通信的方式不再是: conn->set_body()
        //       而是直接conn->send()
        conn->send(resp_body);
        LOG(LogLevel::DEBUG) << resp_body;
    }

private:
    // 从cookie中获取ssid,然后找到对应的session
    session_ptr get_session_by_cookie(WsServer_t::connection_ptr &conn)
    {
        websocketpp::http::parser::request req = conn->get_request();
        std::string cookie = req.get_header("Cookie");
        if (cookie.empty()) // 客户端报文的Cookie字段为空
        {
            // 客户端的报文不携带有会话信息,客户端需要重新登录
            //  给客户端发送响应:
            websocket_send(conn, "hall_ready", false, "请重新登录");
            return NULL;
        }

        // 1.2提取出cookie中的ssid信息
        std::string ssid;
        bool ret1 = get_value_from_cookie(cookie, "SSID", ssid);
        if (!ret1)
        {
            // Cookie当中没有SSID字段,用户需要重新登录
            websocket_send(conn, "hall_ready", false, "找不到SSID信息,请重新登录");
            return NULL;
        }
        // 1.3获取会话
        session_ptr session = _sm.get_session(std::stoi(ssid));
        if (session.get() == nullptr)
        {
            // 会话已经销毁,用户需要重新登录
            websocket_send(conn, "hall_ready", false, "会话已经销毁,请重新登录");
            return NULL;
        }

        return session;
    }

private:
    // 从在线用户管理中移除游戏大厅长连接在线用户
    void remove_game_hall_websocket(WsServer_t::connection_ptr &conn)
    {
        // 1.通过获取cookie字段中的SSID来获取session,然后才知道该用户的用户id  //身份验证
        session_ptr session = get_session_by_cookie(conn);
        if (session.get() == nullptr)
            return;

        // 2.从游戏大厅的在线管理删除该玩家的长连接信息
        _om.Exit_game_hall(session->get_uid());

        // 3.记得将该用户会话设置为具有生命周期的会话
        _sm.set_session_timer(session->get_ssid(), SESSION_TIMEOUT);
    }

    // 从在线用户管理中移除游戏房间长连接在线用户
    void remove_game_room_websocket(WsServer_t::connection_ptr &conn)
    {
        // 1.通过获取cookie字段中的SSID来获取session,然后才知道该用户的用户id  //身份验证
        session_ptr session = get_session_by_cookie(conn);
        if (session.get() == nullptr)
            return;

        // 2.从游戏房间的在线管理删除该玩家的长连接信息
        _om.Exit_game_room(session->get_uid());

        // 3.记得将该用户会话设置为具有生命周期的会话
        _sm.set_session_timer(session->get_ssid(), SESSION_TIMEOUT);

        // 4.将玩家从对应房间中移除,如果房间的用户个数为0,则会将房间信息删除
        _rm.remove_room_by_uid(session->get_uid());
    }

private: // 和http协议相关的请求:
    // 处理所有获取静态资源的http请求:
    void Resource(WsServer_t::connection_ptr &conn)
    {
        // 1.获取该客户端的整个http请求报文:
        websocketpp::http::parser::request req = conn->get_request();

        // 2.获取请求资源的uri:   //如:  /login.html
        std::string uri = req.get_uri();

        // 过滤掉网站图标的请求:
        if (uri == "/favicon.ico")
        {
            return;
        }
        // 3.拼接出完整的资源路径:
        std::string filepath = _web_root + uri; // 如: ./Resource + /login.html

        // 3.1如果uri就是个目录: /
        // 就拼接login.html上去
        if (filepath == "./Resource/")
        {
            filepath += "login.html";
        }

        // 4.获取资源:
        std::string file_content;
        bool ret = File_Util::Read(filepath, file_content);
        if (!ret)
        {
            filepath = "./Resource/404.html";
            File_Util::Read(filepath, file_content);
        }
        // 5.设置响应正文
        conn->set_body(file_content);
        // 6.设置响应状态码(必要)
        conn->set_status(websocketpp::http::status_code::ok);

        // 由于返回的静态资源既有.html、.css、.js，所以下面两个http头部字段不需要手动设置,
        // 浏览器会根据响应正文的内容来自己适配.
        // conn->append_header("Content-Type", "text/html");
        // conn->append_header("Content-Length", std::to_string(file_content.size()));
    }

    // 处理用户注册的http请求:
    void Register(WsServer_t::connection_ptr &conn)
    {
        websocketpp::http::parser::request req = conn->get_request();

        // 1.获取请求正文(用户名/密码, 是序列化的)
        std::string request_body = req.get_body();

        // 2.反序列化(转为json格式)
        Json::Value root;
        bool ret1 = JSON_Util::DeSerialize(request_body, root);
        if (!ret1) // 反序列化失败
        {
            LOG(LogLevel::ERROR) << "反序列化用户注册信息失败";
            return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "请求格式错误");
        }

        // 3.将新用户添加到数据库
        int error_code;
        bool ret2 = _ut.AddUser(root, error_code);
        if (!ret2) // 添加失败
        {
            Json::Value resp;
            switch (error_code)
            {
            case NULL_INPUT: // 客户端用户无输入
                return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名和密码");
            case USER_EXIST: // 用户已经存在
                return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "该用户已经存在");
            default:
                break;
            }
            return;
        }
        // 4. 成功注册新用户之后,需要设置ok状态码,否则服务器默认设置的是500状态码
        http_Json_resp(conn, true, websocketpp::http::status_code::ok, "用户注册成功");
    }

    // 处理用户登录的http请求:
    void Login(WsServer_t::connection_ptr &conn)
    {
        // 1.获取客户端完整的http请求报文
        websocketpp::http::parser::request req = conn->get_request();

        // 2.获取序列化的json格式的请求正文
        std::string request_body = req.get_body();

        // 3.对正文进行反序列化
        Json::Value root;
        bool ret1 = JSON_Util::DeSerialize(request_body, root);
        if (!ret1) // 反序列化失败
        {
            LOG(LogLevel::ERROR) << "反序列化用户注册信息失败";
            return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "请求格式错误");
        }

        // 4.查看数据库是否有该用户(登录验证)
        int error_code;
        bool ret2 = _ut.Login(root, error_code);
        if (!ret2) // 登录失败
        {
            switch (error_code)
            {
            case NULL_INPUT:
                return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名和密码");
            case USER_NOT_EXIST:
                return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名或密码错误");
            default:
                break;
            }
        }

        // 5.验证成功之后给该用户创建会话! ! !
        // 5.1获取用户uid
        int uid = root["id"].asInt(); // id已经在上面Login里从用户表中带出来了
        session_ptr session = _sm.create_session(uid, LOGIN);
        if (session.get() == nullptr)
        {
            LOG(LogLevel::ERROR) << "创建会话失败";
            return http_Json_resp(conn, false, websocketpp::http::status_code::internal_server_error, "服务器创建会话出错");
        }
        // 5.2设置该会话的生命周期
        _sm.set_session_timer(session->get_ssid(), SESSION_TIMEOUT); // 目前仍处于http协议通信,所以会话有生命周期

        // 6.重要!!! : 设置http应答报文的头部字段: Set-Cookie , 将该会话ssid传给客户端浏览器
        // 6.1获取会话id 并设置Cookie的其中一个字段为: "SSID=该会话id"
        std::string cookie_ssid = "SSID=" + std::to_string(session->get_ssid());
        conn->append_header("Set-Cookie", cookie_ssid);

        // 7.给客户端返回登录成功的提示消息
        return http_Json_resp(conn, true, websocketpp::http::status_code::ok, "登录成功");
    }

    // 处理登录成功后进入游戏后,用户获取自己基本信息的http请求:
    void Info(WsServer_t::connection_ptr &conn)
    {
        // 1.首先获取用户http请求报文的Cookie字段
        websocketpp::http::parser::request req = conn->get_request(); // 获取完整请求报文
        std::string cookie_str = req.get_header("Cookie");
        if (cookie_str.empty())
        {
            return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "请重新登录");
        }

        // 2.从Cookie中获取ssid(Cookie字段不止有会话id),然后查找对应session
        std::string ssid;
        // 2.1从Cookie中获取ssid
        bool ret1 = get_value_from_cookie(cookie_str, "SSID", ssid); // "SSID"是上面服务器设置给客户端的
        if (!ret1)
        {
            // Cookie当中没有SSID字段,用户需要重新登录
            return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "找不到SSID信息,请重新登录");
        }
        // 2.2获取会话
        session_ptr session = _sm.get_session(std::stoi(ssid));
        if (session.get() == nullptr)
        {
            // 会话已经销毁,用户需要重新登录
            return http_Json_resp(conn, false, websocketpp::http::status_code::bad_request, "会话已经销毁,请重新登录");
        }

        // 3.获取用户id,根据id从数据库取出用户的详细信息
        int uid = session->get_uid();
        Json::Value user;
        _ut.Select_by_id(uid, user);

        // 4.将用户详细信息发送回客户端
        // 4.1 序列化
        std::string resp_body;
        bool ret2 = JSON_Util::Serialize(user, resp_body);
        // 4.2 设置各字段
        conn->set_body(resp_body); // 响应报文的正文

        std::cout << resp_body << std::endl;

        conn->set_status(websocketpp::http::status_code::ok);
        conn->append_header("Content-Type", "application/json");

        //  5.刷新会话的生命周期
        _sm.set_session_timer(session->get_ssid(), SESSION_TIMEOUT);
    }

private:
    // 给客户端返回一个http响应,正文的格式是json格式字符串
    void http_Json_resp(WsServer_t::connection_ptr conn, bool result,
                        websocketpp::http::status_code::value code, std::string reason)
    {
        Json::Value resp;
        resp["result"] = result;
        resp["reason"] = reason;
        std::string resp_body;
        // 序列化
        JSON_Util::Serialize(resp, resp_body);
        conn->set_status(code);
        conn->append_header("Content-Type", "application/json");
        conn->set_body(resp_body);

        // 请求处理流程结束时websocketpp会自动触发响应发送该http响应报文
        return;
    }

    // 从客户端的http请求的Cookie字段中获取指定值
    bool get_value_from_cookie(const std::string &cookie, const std::string &which_key, std::string &value)
    {
        // 客户端http头部字段的 Cookie字段的格式:
        // Cookie: SSID=xxx;aaa=fff;xxx=yyy;     //key=value

        // 为了获取which_key字段对应的value
        // 1.先按照分号;来切割字符串cookie,得出所有 "key=value"子串 (单个cookie)
        std::vector<std::string> cookie_arr;
        String_Util::split(cookie, ";", cookie_arr);

        // 2.按照等号=切割每一个cookie子串
        for (auto c : cookie_arr)
        {
            std::vector<std::string> tmp;
            String_Util::split(c, "=", tmp);
            // 此时tmp里应该只有两个子串: key和value子串 -> tmp[0]、tmp[1]
            // 3.获取出which_key的value
            if (tmp.size() == 2 && tmp[0] == which_key)
            {
                value = tmp[1];
                return true;
            }
        }
        return false;
    }

public:
    void run(int port)
    {
        // 设置监听端口号
        _server.listen(port);

        // 开始接收新连接
        _server.start_accept();

        // 启动websocket服务器
        _server.run();
    }

private:
    WsServer_t _server;    // websocket服务器
    std::string _web_root; // 静态资源根目录
private:
    // 注意声明顺序越前,则初始化的顺序越前!

    Session_manager _sm; // 会话管理
    Online_Manager _om;  // 在线用户管理
    user_table _ut;      // 用户表管理
    RoomManager _rm;     // 房间管理       //需要用到_ut和_om，所以声明在它们之后
    matcher _mc;         // 用户匹配管理   //需要用到_ut和_om和_rm，所以声明在它们之后
};

